﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Speech;
using Microsoft.Speech.Recognition;
using System.IO;
using Microsoft.Research.Kinect.Audio;
using Microsoft.Speech.AudioFormat;
using com.netkow.KinecTunes.iTunes;

namespace com.netkow.KinecTunes.Kinect
{
    public class Kinector
    {
        private KinectAudioSource audioSource = null;
        private static SpeechRecognitionEngine sre = null;
        private static ItunesConnector itunesConnector = null;
        Stream s = null;
        private Boolean isInUse = false;

        // Raised when a track starts playing
        public delegate void TrackPlayedEventHandler(KinecTrack e);
        public event TrackPlayedEventHandler TrackPlayedEvent;
        // Raisd when a track is paused
        public delegate void TrackPausedEventHandler(object sender);
        public event TrackPausedEventHandler TrackPausedEvent;
        // Raised when a track is stopped
        public delegate void TrackStoppedEventHandler(object sender);
        public event TrackStoppedEventHandler TrackStoppedEvent;
        // Raised when a track has resumed playing
        public delegate void TrackResumedEventHandler(object sender);
        public event TrackResumedEventHandler TrackResumedEvent;
        // Raised when Kinect is being terminated
        public delegate void KinectTerminateEventHandler(object sender);
        public event KinectTerminateEventHandler KinectTerminateEvent;
        // Raised when a speech command was not understood
        public delegate void CommandNotRecognizedEventHandler(object sender);
        public event CommandNotRecognizedEventHandler CommandNotRecognizedEvent;

        public Kinector()
        {
            isInUse = false;
        }

        /// <summary>
        /// Initialize the Kinect device using the dynamically generated iTunes grammar.
        /// </summary>
        /// <returns>true if initialized</returns>
        public bool InitializeKinect()
        {
            bool result = false;

            audioSource = new KinectAudioSource();

            audioSource.FeatureMode = true;
            audioSource.AutomaticGainControl = false; //Important to turn this off for speech recognition
            audioSource.SystemMode = SystemMode.OptibeamArrayOnly; //No AEC

            RecognizerInfo ri = SpeechRecognitionEngine.InstalledRecognizers().Where(r => r.Id == "SR_MS_en-US_Kinect_10.0").FirstOrDefault();

            if (ri == null)
            {
                throw new ApplicationException("Could not load Kinect.");
            }

            sre = new SpeechRecognitionEngine(ri.Id);

            sre.LoadGrammar(BuildItunesGrammar(ri));
            sre.SpeechRecognized += SreSpeechRecognized;
            sre.SpeechHypothesized += SreSpeechHypothesized;
            sre.SpeechRecognitionRejected += SreSpeechRecognitionRejected;

            s = audioSource.Start();
            sre.SetInputToAudioStream(s,
                                        new SpeechAudioFormatInfo(
                                            EncodingFormat.Pcm, 16000, 16, 1,
                                            32000, 2, null));

            sre.RecognizeAsync(RecognizeMode.Multiple);

            result = isInUse = true;
            return result;
        }

        /// <summary>
        /// Build a dynamic iTunes grammar.
        /// </summary>
        /// <param name="ri">the Kinect recognizer info</param>
        /// <returns>the iTunes grammar</returns>
        private Grammar BuildItunesGrammar(RecognizerInfo ri)
        {
            Choices choices = new Choices();
            itunesConnector = new ItunesConnector();
            List<KinecTrack> tracks = itunesConnector.GetFullLibrary();

            // Get list of all Artists and add new command for each
            List<string> artistList = tracks.Select(song => song.Artist).Distinct().ToList();
            foreach (var artist in artistList)
            {
                if (artist != null)
                {
                    choices.Add(string.Format("kinect play artist {0}", artist));
                }
            }

            List<string> songList = tracks.Select(song => song.Name).Distinct().ToList();
            foreach (string song in songList)
            {
                // Remove songs with quotes in them
                if (!string.IsNullOrEmpty(song) && !song.Contains("\""))
                {
                    choices.Add(string.Format("kinect play song {0}", song));
                }
            }

            // Add playback functionality
            choices.Add("kinect pause");
            choices.Add("kinect play");
            choices.Add("kinect stop");
            choices.Add("kinect terminate");

            // Specify the culture to match the recognizer in case we are running in a different culture.
            var gb = new GrammarBuilder();
            gb.Culture = ri.Culture;
            gb.Append(choices);

            return new Grammar(gb);
        }

        /// <summary>
        /// Returns true if the Kinect is currently in use.
        /// </summary>
        public bool kinectInUse()
        {
            return isInUse;
        }

        /// <summary>
        /// Raised via the Kinect when speech was rejected.
        /// </summary>
        private void SreSpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            Console.WriteLine("KinecTunes did not understand you.  Try again!");
            if (CommandNotRecognizedEvent != null)
            {
                CommandNotRecognizedEvent(this);
            }
        }

        /// <summary>
        /// Raised via the Kinect when speech is hypothesized.
        /// </summary>
        private void SreSpeechHypothesized(object sender, SpeechHypothesizedEventArgs e) {}

        /// <summary>
        /// Raised via the Kinect when speech is recognized.
        /// Executes the recognized command.
        /// </summary>
        /// <param name="sender">not used</param>
        /// <param name="e">confidence percentage</param>
        private void SreSpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            // Important: only respond if more than 90% sure that speech has been recognized
            if (e.Result.Confidence >= 0.90)
            {
                string speechText = e.Result.Text;

                if (speechText.Contains("kinect play artist"))
                {
                    PlayArtist(speechText);
                }
                else if (speechText.Contains("kinect play song"))
                {
                    PlaySong(speechText);
                }
                else if (speechText == "kinect play")
                {
                    itunesConnector.Resume();
                    Console.WriteLine("Resumed song playback.");
                    if (TrackResumedEvent != null)
                    {
                        TrackResumedEvent(this);
                    }
                }
                else if (speechText == "kinect pause")
                {
                    itunesConnector.Pause();
                    Console.WriteLine("Paused song.");
                    if (TrackPausedEvent != null)
                    {
                        TrackPausedEvent(this);
                    }
                }
                else if (speechText == "kinect stop")
                {
                    itunesConnector.Stop();
                    Console.WriteLine("Stopped song.");
                    if (TrackStoppedEvent != null)
                    {
                        TrackStoppedEvent(this);
                    }
                }
                else if (speechText == "kinect terminate")
                {
                    Terminate();
                }
            }
        }

        /// <summary>
        /// Stop the Kinect device.
        /// </summary>
        public void Terminate()
        {
            sre.RecognizeAsyncStop();
            Console.WriteLine("Kinect disconnected.");
            if (KinectTerminateEvent != null)
            {
                KinectTerminateEvent(this);
            }
        }

        /// <summary>
        /// Parse the artist or song name out of the given speech text.
        /// Assumes first 3 words are: "kinect play [artist|song]"
        /// </summary>
        /// <param name="speechText">the recognized speech text</param>
        /// <returns>artist or song name</returns>
        private string ParseArtistOrSong(string speechText)
        {
            string result = string.Empty;

            // Extract artist or song name
            string[] words = speechText.Split(' ');

            string artistOrSong = string.Empty;
            for (int i = 3; i < words.Length; i++)
            {
                artistOrSong += words[i] + " ";
            }

            result = artistOrSong.Trim();

            return result;
        }

        /// <summary>
        /// Play a random song from the chosen Artist.
        /// </summary>
        /// <param name="speechText">the recognized speech text containing the artist name</param>
        private void PlayArtist(string speechText)
        {
            string artistName = this.ParseArtistOrSong(speechText);

            // Pick random song to play
            int randomSongIndex = itunesConnector.GetRandomSongFromArtist(artistName);

            if (randomSongIndex != -1)
            {
                KinecTrack track = itunesConnector.Play(randomSongIndex);
                Console.WriteLine(string.Format("Now Playing Artist: {0}, Song: {1}", track.Artist, track.Name));

                // raise track played event
                if (TrackPlayedEvent != null)
                {
                    TrackPlayedEvent(track);
                }
            }
        }

        /// <summary>
        /// Play the selected song.
        /// </summary>
        /// <param name="speechText">the recognized speech text containing the song name</param>
        private void PlaySong(string speechText)
        {
            string song = this.ParseArtistOrSong(speechText);

            KinecTrack track = itunesConnector.Play(song);
            if (track != null)
            {
                Console.WriteLine(string.Format("Now Playing Artist: {0}, Song: {1}", track.Artist, track.Name));

                // raise track played event
                if (TrackPlayedEvent != null)
                {
                    TrackPlayedEvent(track);
                }
            }
        }
    }
}
